#include <devices.hpp>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <mdshow.hpp>
#include <common.hpp>
#define PPVOID(x) reinterpret_cast<LPVOID *>(&x)
#endif //WIN32

static capture_device_info_t *
capture_device_info_append(const char *name,
                           const char *uid,
                           capture_device_info_t *);
static capture_device_info_t *
capture_device_info_new(const char *name,
                        const char *uid);
#ifdef WIN32
static long
get_device_property(LPCWSTR propStr,
                    IPropertyBag *pBag,
                    char **value);
#endif

static capture_device_info_t *
capture_device_info_new(const char *name, const char *uid)
{
    capture_device_info_t   *cdi;
    unsigned int            len;

    cdi = (capture_device_info_t *)malloc(sizeof(capture_device_info_t));

    len       = strlen(name);
    cdi->name = (char *)malloc(len + 1);

    memcpy(cdi->name, name, len + 1);
    cdi->name[len] = '\0';

    len      = strlen(uid);
    cdi->uid = (char *)malloc(len + 1);

    memcpy(cdi->uid, uid, len + 1);
    cdi->uid[len] = '\0';

    cdi->next = NULL;

    return cdi;
}

static capture_device_info_t *
capture_device_info_append(const char *name,
                           const char *uid,
                           capture_device_info_t *cdi)
{
    capture_device_info_t *next;
    if (!cdi) {
        next = capture_device_info_new(name, uid);
        cdi = next;
    } else {
        next = cdi;

        while (next->next)
            next = next->next;

        next->next = capture_device_info_new(name, uid);
        next       = next->next;
    }

    return next;
}

void
capture_device_info_free(capture_device_info_t *cdi)
{
    if (!cdi)
        return;

    if (cdi->next)
        capture_device_info_free(cdi->next);

    free(cdi->name);
    free(cdi->uid);
    free(cdi);

    cdi = NULL;
}

#ifdef WIN32
static HRESULT
get_device_property(LPCWSTR propStr, IPropertyBag *pBag, char **value)
{
    HRESULT 	 result;
    unsigned int len;
    VARIANT 	 vStr;

    VariantInit(&vStr);
    vStr.vt = VT_BSTR;

    result = pBag->Read(propStr, &vStr, 0);
    if (result == S_OK) {
        len    = wcslen(vStr.bstrVal) + 1;
        *value = new char[len];

        WideCharToMultiByte(CP_ACP, 0, vStr.bstrVal, -1, *value, len, 0, 0);
    }
    VariantClear(&vStr);

    return result;
}

capture_device_info_t *
list_capture_devices(int *status)
{
    HRESULT        result;
    ICreateDevEnum *pDevEnum;
    IEnumMoniker   *pEnumMoniker;
    IPropertyBag   *pBag;
    IMoniker       *pMoniker;
    char           *name;
    char           *uid;

    /* lista de dispositivos */
    capture_device_info_t *cdi;

    pDevEnum     = NULL;
    pMoniker     = NULL;
    pEnumMoniker = NULL;
    /* Para leer las propiedades */
    pBag = NULL;
    cdi  = NULL;
    name = NULL;
    uid  = NULL;

    *status = NO_DEVICES_FOUND;

    result = CoInitialize(NULL);

    if (result != S_OK && result != S_FALSE) {
        *status = CO_INITIALIZATION_FAILED;
        return NULL;
    }
    result = CoCreateInstance(CLSID_SystemDeviceEnum,
                              0,
                              CLSCTX_INPROC_SERVER,
                              IID_ICreateDevEnum,
                              PPVOID(pDevEnum));
    if (FAILED(result))
        goto abort;

    if (!pDevEnum)
        goto abort;

    result = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
             &pEnumMoniker,
             0);
    if (FAILED(result))
        goto abort;

    if (!pEnumMoniker)
        goto abort;

    while (pEnumMoniker->Next(1, &pMoniker, 0) == S_OK) {
        /* Obtenemos IPropertyBag */
        result = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, PPVOID(pBag));
        if (SUCCEEDED(result)) {
            /* Nombre Amigable */
            result = get_device_property(L"FriendlyName", pBag, &name);
            if (SUCCEEDED(result)) {
                /* Ruta */
                result = get_device_property(L"DevicePath", pBag, &uid);
                if (SUCCEEDED(result)) {
                    /* Listo, agregamos */
                    if (!cdi)
                        cdi = capture_device_info_append(name, uid, cdi);
                    else
                        capture_device_info_append(name, uid, cdi);
                    *status = OK_STATUS; /* Almenos un dispositvo? Ah! Ok */
                }
            }
        }
        /* Clean Up */
        delete[] uid;
        delete[] name;

        pMoniker->Release();
        pBag->Release();

        name     = NULL;
        uid      = NULL;
        pMoniker = NULL;
        pBag     = NULL;
    }

    if (pDevEnum)
        pDevEnum->Release();
    if (pEnumMoniker)
        pEnumMoniker->Release();
    CoUninitialize();

    return cdi;

abort:
    if (pDevEnum)
        pDevEnum->Release();
    if (pEnumMoniker)
        pEnumMoniker->Release();
    if (pMoniker)
        pMoniker->Release();
    if (pBag)
        pBag->Release();
    if (uid)
        delete[] uid;
    if (name)
        delete[] name;
    if (cdi) {
        if (*status != OK_STATUS) {
            capture_device_info_free(cdi);
            cdi = NULL;
        } else
            return cdi;
    }
    pDevEnum     = NULL;
    pEnumMoniker = NULL;
    pMoniker     = NULL;
    pBag         = NULL;
    uid          = NULL;
    name         = NULL;

    return NULL;
}
#endif

#ifdef LINUX
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <common.hpp>
#include <libv4l2.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <errno.h>
#include <error.h>

static int
xioctl(int fd, int request, void *arg)
{
    int result;

    do {
        result = ioctl(fd, request, arg);
    } while (result == -1 && errno == EINTR);

    return  result;
}

capture_device_info_t *
list_capture_devices(int *status)
{
    capture_device_info_t *cid;

    cid = NULL;
    for (int index = 0 ; index < 64 ; index++) {
        int                    fd;
        struct v4l2_capability caps;
        char                   filename[128] = {'\0'};

        snprintf(filename, 128, "/dev/video%d", index);
#ifdef DEBUG
        fprintf(stderr, "trying to open %s...\n", filename);
#endif
        fd = open(filename, O_RDONLY);
        if (fd == -1) {
#ifdef DEBUG
            fprintf(stderr, "\terror %d: %s\n", errno, strerror(errno));
#endif
            continue;
        } else {
#ifdef DEBUG
            fprintf(stderr, "\tdevice %s opened...\n", filename);
#endif
        }

        if (xioctl(fd, VIDIOC_QUERYCAP, &caps) != -1) {
            if (caps.capabilities & V4L2_CAP_STREAMING) {
                const char *name = (const char *)caps.card;
                if (!cid)
                    cid = capture_device_info_append(name, filename, cid);
                else
                    capture_device_info_append(name, filename, cid);

                *status = OK_STATUS;
            }
        }
        close(fd);
    }
    return cid;
}
#endif
